﻿#region Using References

using System;
using System.Diagnostics.Contracts;
using System.Threading;
using Microsoft.Practices.ServiceLocation;

#endregion

namespace Beaker.Services.Locator
{
	/// <summary>
	/// Custom ServiceLocator that solves some problems with  <see cref="Microsoft.Practices.ServiceLocation.ServiceLocator"/>.
	/// </summary>
	/// <remarks>
	/// The microsoft version doesn't handle null references gracefully
	/// </remarks>
	public static class ServiceLocator
	{
		private static Func<IServiceLocator> _currentServiceLocatorProvider;

		/// <summary>
		/// Get the current service locator
		/// </summary>
		public static IServiceLocator Current
		{
			get
			{
				if( _currentServiceLocatorProvider != null )
					return _currentServiceLocatorProvider();

				return null;
			}
		}

		/// <summary>
		/// Sets the locator to the given item
		/// </summary>
		/// <param name="serviceLocator">The service locator to set.</param>
		public static void SetLocator( IServiceLocator serviceLocator )
		{
			Contract.Requires<ArgumentNullException>( serviceLocator != null );
			SetLocatorProvider( () => serviceLocator );
		}

		/// <summary>
		/// Set a function that will return the current service locator
		/// </summary>
		/// <param name="newProvider">The function that will return the current service locator</param>
		public static void SetLocatorProvider( Func<IServiceLocator> newProvider )
		{
			Contract.Requires<ArgumentNullException>( newProvider != null );
			Contract.Ensures( _currentServiceLocatorProvider == newProvider );

			Interlocked.Exchange( ref _currentServiceLocatorProvider, newProvider );

			// set it also for the microsoft service locator for system that use it
			Microsoft.Practices.ServiceLocation.ServiceLocator.SetLocatorProvider(
				() => newProvider()
			);
		}

		/// <summary>
		/// Clear the function that returnes the current service locator
		/// </summary>
		public static void ClearLocatorProvider()
		{
			Contract.Ensures( _currentServiceLocatorProvider == null );

			Interlocked.Exchange( ref _currentServiceLocatorProvider, null );
		}

		/// <summary>
		/// Provides the given service by searching all possible locators, providers and default values
		/// </summary>
		/// <typeparam name="T">Type of the service to provide</typeparam>
		/// <param name="defaultService">The default service to use, if lcoator does not result in one</param>
		/// <remarks>
		/// This is ment for implementing the Current or Default static property on the default implementation
		/// First tries to get the default locator.
		/// Then asks it to give the instance.
		/// If this failes return the default Service given
		/// </remarks>
		internal static T Provide<T>( T defaultService )
			where T : class
		{
			Contract.Requires<ArgumentNullException>( defaultService != null );
			Contract.Ensures( Contract.Result<T>() != null );

			IServiceLocator locator = Current;
			if( locator != null )
			{
				var service = locator.GetInstance<T>();
				if( service != null )
					return service;
			}

			return defaultService;
		}
	}

}
